////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Copyright (c) Microsoft Corporation.  All rights reserved.
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
namespace Gadgeteer.Interfaces
{
    using System;
    using Microsoft.SPOT;
    using Microsoft.SPOT.Hardware;
    using Gadgeteer.Modules;

    internal class NativeInterruptInput : Socket.SocketInterfaces.InterruptInput
    {
        private InterruptPort _port;

        public NativeInterruptInput(Socket socket, Socket.Pin pin, GlitchFilterMode glitchFilterMode, ResistorMode resistorMode, InterruptMode interruptMode, Module module, Cpu.Pin cpuPin)
        {
            if (cpuPin == Cpu.Pin.GPIO_NONE)
            {
                // this is a mainboard error but should not happen since we check for this, but it doesnt hurt to double-check
                throw Socket.InvalidSocketException.FunctionalityException(socket, "InterruptInput");
            }

            _port = new InterruptPort(cpuPin, glitchFilterMode == GlitchFilterMode.On, (Port.ResistorMode)resistorMode, (Port.InterruptMode)interruptMode);
        }

        public override bool Read()
        {
            return _port.Read();
        }

        protected override void OnInterruptFirstSubscribed()
        {
            _port.OnInterrupt += OnPortInterrupt;
        }

        protected override void OnInterruptLastUnsubscribed()
        {
            _port.OnInterrupt -= OnPortInterrupt;
        }

        private void OnPortInterrupt(uint data1, uint data2, DateTime time)
        {
            RaiseInterrupt(data2 > 0);
        }

        public override void Dispose()
        {
            _port.Dispose();
        }
    }


    /// <summary>
    /// Represents interrupt input on a single pin not bound to a specific <see cref="T:Gadgeteer.Socket"/>.
    /// </summary>
    public class InterruptInput : IDisposable
    {
        internal readonly Socket.SocketInterfaces.InterruptInput Interface;

        /// <summary>
        /// Gets or sets a value that determines how the <see cref="Interrupt"/> event is raised.
        /// </summary>
        /// <remarks>
        /// An interrupt may occur on a thread other than the application thread. 
        /// When <see cref="SynchronousUnsafeEventInvocation"/> is <b>false</b> (the default),
        /// the <see cref="Interrupt"/> event, which is raised in response to the interrupt on the interface,
        /// is not raised immediately, instead it is queued for raising on the application's dispatcher thread.  However, 
        /// when <see cref="SynchronousUnsafeEventInvocation"/> is <b>true</b>, the 
        /// <see cref="Interrupt"/> event is raised immediately on the same thread that generated the interrupt.  
        /// This results in faster interrupt processing and may be useful to respond to realtime events, but extra care 
        /// must be taken when using this facility to be thread-safe, i.e. to handle issues such as locking, 
        /// atomic reading/writing of streams/files, deadlock avoidance, etc.
        /// </remarks>
        public bool SynchronousUnsafeEventInvocation;

        // Note: A constructor summary is auto-generated by the doc builder.
        /// <summary></summary>
        /// <param name="socket">The socket for the interrupt input interface.</param>
        /// <param name="pin">The pin used by the interrupt input interface.</param>
        /// <param name="glitchFilterMode">
        ///  A value from the <see cref="GlitchFilterMode"/> enumeration that specifies 
        ///  whether to enable the glitch filter on this interrupt input interface.
        /// </param>
        /// <param name="resistorMode">
        ///  A value from the <see cref="ResistorMode"/> enumeration that establishes a default state for the interrupt input interface. N.B. .NET Gadgeteer mainboards are only required to support ResistorMode.PullUp on interruptable GPIOs and are never required to support ResistorMode.PullDown; consider putting the resistor on the module itself.
        /// </param>
        /// <param name="interruptMode">
        ///  A value from the <see cref="InterruptMode"/> enumeration that establishes the requisite conditions 
        ///  for the interface port to generate an interrupt.
        /// </param>
        /// <param name="module">The module using this interrupt input interface, which can be null if unspecified.</param>
        public InterruptInput(Socket socket, Socket.Pin pin, GlitchFilterMode glitchFilterMode, ResistorMode resistorMode, InterruptMode interruptMode, Module module)
        {
            Cpu.Pin reservedPin = socket.ReservePin(pin, module);

            // native implementation is preferred to an indirected one
            if (reservedPin == Cpu.Pin.GPIO_NONE && socket.InterruptIndirector != null)
                Interface = socket.InterruptIndirector(socket, pin, glitchFilterMode, resistorMode, interruptMode, module);

            else
                Interface = new NativeInterruptInput(socket, pin, glitchFilterMode, resistorMode, interruptMode, module, reservedPin);
        }


        /// <summary>
        /// Reads a Boolean value at the InterruptInput interface port input. 
        /// </summary>
        /// <returns>A Boolean value that indicates the current value of the port as either 0 or 1).</returns>
        public bool Read()
        {
            return Interface.Read();
        }

        /// <summary>
        /// Represents the delegate used for the <see cref="Interrupt"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="InterruptInput"/> object that raised the event.</param>
        /// <param name="value"><b>true</b> if the the value received from the interrupt is greater than zero; otherwise, <b>false</b>.</param>
        public delegate void InterruptEventHandler(InterruptInput sender, bool value);
        private InterruptEventHandler _interruptHandler;

        /// <summary>
        /// Raised when the InterruptInput interface detects an interrupt.
        /// </summary>
        public event InterruptEventHandler Interrupt
        {
            add
            {
                if (value == null)
                    return;

                if (_interruptHandler == null)
                    Interface.Interrupt += OnInterfaceInterrupt;

                _interruptHandler += value;
            }
            remove
            {
                if (value == null)
                    return;

                _interruptHandler -= value;

                if (_interruptHandler == null)
                    Interface.Interrupt -= OnInterfaceInterrupt;
            }
        }

        private void OnInterfaceInterrupt(Socket.SocketInterfaces.InterruptInput sender, bool value)
        {
            OnInterruptEvent(this, value);
        }

        /// <summary>
        /// Raises the <see cref="Interrupt"/> event.
        /// </summary>
        /// <param name="sender">The <see cref="InterruptInput"/> object that raised the event.</param>
        /// <param name="value"><b>true</b> if the the value received from the interrupt is greater than zero; otherwise, <b>false</b>.</param>
        protected virtual void OnInterruptEvent(InterruptInput sender, bool value)
        {
            InterruptEventHandler handler = _interruptHandler;

            if (handler == null)
                return;

            if (SynchronousUnsafeEventInvocation)
            {
                try { handler(sender, value); }
                catch { }
            }
            else
            {
                if (Program.CheckAndInvoke(handler, handler, sender, value))
                    handler(sender, value);
            }
        }

        /// <summary>
        /// Returns the <see cref="Socket.SocketInterfaces.InterruptInput" /> for an <see cref="InterruptInput" /> interface.
        /// </summary>
        /// <param name="this">An instance of <see cref="InterruptInput" />.</param>
        /// <returns>The <see cref="Socket.SocketInterfaces.InterruptInput" /> for <paramref name="this"/>.</returns>
        public static explicit operator Socket.SocketInterfaces.InterruptInput(InterruptInput @this)
        {
            return @this.Interface;
        }

        /// <summary>
        /// Releases all resources used by the interface.
        /// </summary>
        public void Dispose()
        {
            Interface.Dispose();
        }
    }
}
